﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace BUS
{
    public class OurSymAlgo
    {
        /// <summary>
        /// khởi tạo thuật toán mã hóa đối xứng
        /// </summary>
        /// <param name="SymAlgo">1:DES, 2: TripleDES, 3:Rijndael, 4: RC2</param>
        /// <returns>thuật toán mã hóa</returns>
        static private SymmetricAlgorithm CreateSymmetricAlgorithm(int SymAlgo)
        {
            if (SymAlgo == 2)
                return TripleDESCryptoServiceProvider.Create();
            else
                if (SymAlgo == 3)
                    return RijndaelManaged.Create();
                else
                    if (SymAlgo == 4)
                        return RC2CryptoServiceProvider.Create();
                    else
                        return DESCryptoServiceProvider.Create();
        }

        /// <summary>
        /// Tạo Cipher mode
        /// </summary>
        /// <param name="cipher">
        /// 1:ECB,
        /// 2:CBC,
        /// 3:CFB,
        /// 4:OFB,
        /// 5:CTS
        /// </param>
        /// <returns>ciphermode</returns>
        static private CipherMode CreateCipher(int cipher)
        {
            if (cipher == 2)
                return CipherMode.CBC;
            else
                if (cipher == 3)
                    return CipherMode.CFB;
                else
                    if (cipher == 4)
                        return CipherMode.OFB;
                    else
                        if (cipher == 5)
                            return CipherMode.CTS;
                        else
                            return CipherMode.ECB;
        }

        /// <summary>
        /// Tạo padding mode
        /// </summary>
        /// <param name="padding">
        /// 1: PKCS7
        /// 2: ISO10126
        /// 3: ANSIX923
        /// 4: Zeros
        /// 5: None
        /// </param>
        /// <returns> paddingmode</returns>
        static private PaddingMode CreatePadding(int padding)
        {
            if (padding == 2)
                return PaddingMode.ISO10126;
            else
                if (padding == 3)
                    return PaddingMode.ANSIX923;
                else
                    if (padding == 4)
                        return PaddingMode.Zeros;
                    else
                        if (padding == 5)
                            return PaddingMode.None;
                        else
                            return PaddingMode.PKCS7;
        }

        /// <summary>
        /// Chuyển 1 số nguyên ra mãng byte
        /// </summary>
        /// <param name="Int"> số nguyên</param>
        /// <returns>1 mảng byte</returns>
        static byte[] Int32ToBytes(int Int)
        {
            return BitConverter.GetBytes(Int);

        }

        /// <summary>
        /// Mã hóa 1 mảng byte thành 1 mảng byte
        /// </summary>
        /// <param name="_plainbytes">mảng byte cần được mã hóa</param>
        /// <param name="Key">Key được phát sinh</param>
        /// <param name="IV">IV được phát sinh</param>
        /// <param name="cm">CipherMode được chọn</param>
        /// <param name="pm">Paddingmode được chọn</param>
        /// <param name="SymAlgo">Thuật toán mã hóa được chọn</param>
        /// <param name="receiverPublicKey">public key của người nhận</param>
        /// <returns>mảng byte..ghi ra file</returns>
        static public byte[] Encrypt(byte[] _plainbytes,
                                     ref byte[] Key,
                                     ref byte[] IV,
                                     int cm,
                                     int pm,
                                     int SymAlgo,
                                     string receiverPublicKey)
        {
            byte[] cypherbytes = { };
          
            SymmetricAlgorithm sa = CreateSymmetricAlgorithm(SymAlgo);
            sa.Mode = CreateCipher(cm);
            sa.Padding = CreatePadding(pm);
            sa.GenerateKey();
            sa.GenerateIV();
            
            Key = sa.Key;
            IV = sa.IV;

            // chuyển thuật toán thành byte
            byte[] SymAlgoBytes = new byte[4];
            SymAlgoBytes = Int32ToBytes(SymAlgo);
            //chuyển cipher và padding thành bytes
           
    
            byte[] modeBytes = new byte[4];
            modeBytes = Int32ToBytes(cm);
            
            byte[] paddingBytes = new byte[4];
            paddingBytes = Int32ToBytes(pm);
           


            #region mã hóa khóa k
            byte[] rsaEncryptedKey = OurRSA.Encrypt(receiverPublicKey, Key);
            byte[] keyLen = Int32ToBytes(rsaEncryptedKey.Length);
            byte[] IVLen = Int32ToBytes(IV.Length);

            #endregion

          
            ICryptoTransform transform = sa.CreateEncryptor();
            MemoryStream MS = new MemoryStream();
            CryptoStream cs = new CryptoStream(MS, transform, CryptoStreamMode.Write);
            cs.Write(_plainbytes, 0, _plainbytes.Length);
            cs.FlushFinalBlock();
            cypherbytes = MS.ToArray();

            int size = 4 + 4 + 4 + 4 + rsaEncryptedKey.Length + 4 + IV.Length+ cypherbytes.Length;
            byte[] res = new byte[size];
            SymAlgoBytes.CopyTo(res, 0);
            modeBytes.CopyTo(res, 4);
            paddingBytes.CopyTo(res, 8);
            keyLen.CopyTo(res, 12);
            rsaEncryptedKey.CopyTo(res, 16);
            IVLen.CopyTo(res, 16 + rsaEncryptedKey.Length);
            IV.CopyTo(res, 20 + rsaEncryptedKey.Length);
            cypherbytes.CopyTo(res, 20 + rsaEncryptedKey.Length + IV.Length);


            return res;
        }

        /// <summary>
        /// Giải mã 1 cipher ra plain(giải mã khi đã giải mã KEY được phát sinh)
        /// </summary>
        /// <param name="_cypherbytes">cipher</param>
        /// <param name="Key">Key(đã được giải mã)</param>
        /// <param name="IV">IV</param>
        /// <param name="cm">ciphermode</param>
        /// <param name="pm">paddingmode</param>
        /// <param name="SymAlgo">thuật toán được chọn</param>
        /// <returns>1 mảng byte..dùng mảng này ghi ra file kết quả</returns>
        static public byte[] Decrypt(byte[] _cypherbytes,
                                     byte[] Key,
                                     byte[] IV,
                                     int cm,
                                     int pm,
                                     int SymAlgo)
        {
          
            SymmetricAlgorithm sa = CreateSymmetricAlgorithm(SymAlgo);
            sa.Mode = CreateCipher(cm);
            sa.Padding = CreatePadding(pm);

            sa.Key = Key;
            sa.IV = IV;

            ICryptoTransform transform = sa.CreateDecryptor();
            MemoryStream MS = new MemoryStream(_cypherbytes);
            CryptoStream cs = new CryptoStream(MS, transform, CryptoStreamMode.Read);
            byte[] plainbytes = new byte[_cypherbytes.Length];
            cs.Read(plainbytes, 0, _cypherbytes.Length);

            return plainbytes;
        }

        /// <summary>
        /// Load thông tin cần thiết cho quá trình giải mã đối xứng
        /// từ 1 mảng byte(sau khi đọc từ file cần giải mã)
        /// </summary>
        /// <param name="bytesBeforeDecrypting">mảng byte sau khi được đọc lên từ file</param>
        /// <param name="SymAlgo">Thuật toán cần xác định</param>
        /// <param name="cipherMode">Ciphermode</param>
        /// <param name="paddingMode">Paddingmode</param>
        /// <param name="rsaEncryptedKey">Key đạ được mã hóa bằng rsa</param>
        /// <param name="IV">IV</param>
        /// <returns>mảng cipher cần phải giải mã tiếp</returns>
        static byte[] LoadInFoBeforeDecrypting(byte[] bytesBeforeDecrypting,
                                                ref int SymAlgo,
                                                ref int cipherMode,
                                                ref int paddingMode,
                                                ref byte[] rsaEncryptedKey,
                                                ref byte[] IV)
        {
            int keyLen;
            int IVLen;

            byte[] SymAlgoBytes = new byte[4];
            byte[] modeBytes = new byte[4];
            byte[] paddingBytes = new byte[4];
            byte[] keyLenBytes = new byte[4];
            byte[] IVLenBytes = new byte[4];
            

            Array.Copy(bytesBeforeDecrypting, 0, SymAlgoBytes, 0, 4);
            SymAlgo = BitConverter.ToInt32(SymAlgoBytes, 0);
            Array.Copy(bytesBeforeDecrypting, 4, modeBytes, 0, 4);
            cipherMode = BitConverter.ToInt32(modeBytes, 0);
            Array.Copy(bytesBeforeDecrypting, 8, paddingBytes, 0, 4);
            paddingMode = BitConverter.ToInt32(paddingBytes, 0);
            Array.Copy(bytesBeforeDecrypting, 12, keyLenBytes, 0, 4);
            keyLen = BitConverter.ToInt32(keyLenBytes, 0);

            rsaEncryptedKey = new byte[keyLen];
            
            Array.Copy(bytesBeforeDecrypting, 16, rsaEncryptedKey,0, keyLen);
            Array.Copy(bytesBeforeDecrypting, 16 +keyLen, IVLenBytes, 0, 4);
            IVLen=BitConverter.ToInt32(IVLenBytes, 0);
            IV = new byte[IVLen];
            Array.Copy(bytesBeforeDecrypting, 20 + keyLen, IV, 0, IVLen);
            int cipherLen=bytesBeforeDecrypting.Length - 20 - keyLen - IVLen;
            byte[] cipherBytes = new byte[cipherLen];
            Array.Copy(bytesBeforeDecrypting, 20 + keyLen + IVLen, cipherBytes, 0, cipherLen);

            return cipherBytes;
            
        }

        /// <summary>
        /// tiến hành giải mã hoàn chỉnh file
        /// </summary>
        /// <param name="fileName"> tên file cần giải mã</param>
        /// <param name="privateKey">privatekey của người giải mã</param>
        /// <returns>mảng byte được giãi mã hoàn chỉnh</returns>
        public static byte[] Decrypt(string fileName,string privateKey)
        {
            // doc file ra byte[]
            if (!File.Exists(fileName))
                return null;
            byte[] bytesBefore = File.ReadAllBytes(fileName);
            int SymAlgo=0;
            int cipherMode=0;
            int paddingMode=0;
            byte[] rsaEncryptedKey = { };
            byte[] IV = { };
            byte[] cipher = { };
            cipher = LoadInFoBeforeDecrypting(bytesBefore, ref SymAlgo, ref cipherMode, ref paddingMode, ref rsaEncryptedKey, ref IV);


            // giải mã khóa K bằng privateKey
            byte[] key = OurRSA.Decrypt(rsaEncryptedKey, privateKey);
            byte[] plainBytes = Decrypt(cipher, key, IV, cipherMode, paddingMode, SymAlgo);
            return plainBytes;

        }
    }
}
